#include "BinaryOperation.h"
#include "../Exceptions.h"
#include "../GeneralVisitor.h"

BinaryOperation::BinaryOperation(BinaryOperationType iOperation, TreeNode *iLeft, TreeNode *iRight)
{
    fOperation = iOperation;
    fLeft = iLeft;
    fRight = iRight;
}

BinaryOperation::~BinaryOperation()
{
    delete fLeft;
    delete fRight;
}

void BinaryOperation::CheckVariables(set<string>& oVariables, string iParentName)
{
    this->fLeft->CheckVariables(oVariables, iParentName);
    this->fRight->CheckVariables(oVariables, iParentName);
}

void BinaryOperation::CheckFunctionCalls(set<FunctionDefinition*>& iAllFunctions, string iParentName)
{
    this->fLeft->CheckFunctionCalls(iAllFunctions, iParentName);
    this->fRight->CheckFunctionCalls(iAllFunctions, iParentName);
}

void BinaryOperation::CheckClassConstructions(set<ClassDefinition*>& iAllClasses, string iParentName)
{
    this->fLeft->CheckClassConstructions(iAllClasses, iParentName);
    this->fRight->CheckClassConstructions(iAllClasses, iParentName);
}

void BinaryOperation::CheckExpressionTypes(map<string, ExpressionType*>& iVarTypes, string iParentName)
{
    this->fLeft->CheckExpressionTypes(iVarTypes, iParentName);
    this->fRight->CheckExpressionTypes(iVarTypes, iParentName);

    ExpressionType* lLeftType = this->fLeft->GetType(iVarTypes, iParentName);
    ExpressionType* lRightType = this->fRight->GetType(iVarTypes, iParentName);

    if(!lLeftType->fResolveTypeAtRuntime && !lRightType->fResolveTypeAtRuntime)
    {
        ValueType lLeftValType = lLeftType->fType;
        ValueType lRightValType = lRightType->fType;

        bool lbOK = false;
        bool lbLeftIsNum = false;
        bool lbRightIsNum = false;
        bool lbLeftIsString = false;
        bool lbRightIsString = false;
        bool lbLeftIsBool = false;
        bool lbRightIsBool = false;

        if(lLeftValType == LongInteger || lLeftValType == Double)
            lbLeftIsNum = true;

        if(lRightValType == LongInteger || lRightValType == Double)
            lbRightIsNum = true;

        if(lLeftValType == String)
            lbLeftIsString = true;

        if(lRightValType == String)
            lbRightIsString = true;

        // aritmeticke operace - povolujeme jenom cisla
        if(fOperation == Plus || fOperation == Minus || fOperation == Multiply || fOperation == Divide)
        {
            if(lbLeftIsNum && lbRightIsNum)
                lbOK = true;
        }

        // "secteni" stringu
        if(fOperation == Plus)
        {
            if(lbLeftIsString && lbRightIsString)
                lbOK = true;
        }

        // <, >, <=, >= - pouze pro ciselne typy
        if(fOperation == Less || fOperation == Greater || fOperation == LessOrEqual || fOperation == GreaterOrEqual)
        {
            if(lbLeftIsNum && lbRightIsNum)
                lbOK = true;
        }

        // porovnani... oba musi byt zaroven cislo, string nebo boolean
        if(fOperation == Equal || fOperation == NotEqual)
        {
            if(lbLeftIsNum && lbRightIsNum)
                lbOK = true;

            if(lbLeftIsString && lbRightIsString)
                lbOK = true;

            if(lbLeftIsBool && lbRightIsBool)
                lbOK = true;
        }

        if(!lbOK)
        {
            delete lLeftType;
            delete lRightType;
            throw IncompatibleOperationAndTypesException(iParentName, lLeftValType, lRightValType, fOperation);
        }
    }

    delete lLeftType;
    delete lRightType;
}

bool BinaryOperation::AreTypesOK(ValueType iLeftType, ValueType iRightType, BinaryOperationType iOp)
{
    if(iOp == Less || iOp == Greater || iOp == LessOrEqual || iOp == GreaterOrEqual)
    {
        if((iLeftType != LongInteger && iLeftType != Double) || (iRightType != LongInteger && iRightType != Double))
        {
            return false;
        }
    }
    else if(iOp == Modulo)
    {
        if(iLeftType != LongInteger || iRightType != LongInteger)
        {
            return false;
        }
    }
    else if(iOp == Equal || iOp == NotEqual || iOp == And || iOp == Or)
    {
        if((iLeftType == Boolean && iRightType != Boolean)
            || (iLeftType == String && iRightType != String)
            || (
                    (iLeftType == LongInteger || iLeftType == Double)
                    &&
                    (iRightType != LongInteger && iRightType != Double)
                )
           || (iLeftType != Boolean && iLeftType != LongInteger && iLeftType != Double && iLeftType != String))
        {
            return false;
        }
    }
    else if(iOp == Minus || iOp == Multiply || iOp == Divide)
    {
        if((iLeftType != LongInteger && iLeftType != Double) || (iRightType != LongInteger && iRightType != Double))
        {
            return false;
        }
    }
    else if(iOp == Plus)
    {
        if((iLeftType == String && iRightType != String)
            || (
                    (iLeftType == LongInteger || iLeftType == Double)
                    &&
                    (iRightType != LongInteger && iRightType != Double)
                )
           || (iLeftType != LongInteger && iLeftType != Double && iLeftType != String))
        {
            return false;
        }
    }

    return true;
}

ExpressionType* BinaryOperation::GetType(map<string, ExpressionType*>& iVarTypes, string iParentName)
{
    ExpressionType* lLeftType = this->fLeft->GetType(iVarTypes, iParentName);
    ExpressionType* lRightType = this->fRight->GetType(iVarTypes, iParentName);
    ExpressionType* lResult = 0;

    if(!lLeftType->fResolveTypeAtRuntime && !lRightType->fResolveTypeAtRuntime)
    {
        ValueType lLeftValType = lLeftType->fType;
        ValueType lRightValType = lRightType->fType;

        if(!this->AreTypesOK(lLeftValType, lRightValType, fOperation))
        {
            delete lLeftType;
            delete lRightType;
            throw IncompatibleOperationAndTypesException(iParentName, lLeftValType, lRightValType, fOperation);
        }

        if(fOperation == Less || fOperation == Greater || fOperation == LessOrEqual || fOperation == GreaterOrEqual)
        {
            lResult = new ExpressionType(Boolean, false);
        }
        else if(fOperation == Equal || fOperation == NotEqual || fOperation == And || fOperation == Or)
        {
            lResult = new ExpressionType(Boolean, false);
        }
        else if(fOperation == Minus || fOperation == Multiply || fOperation == Divide)
        {
            if(lLeftValType == Double || lRightValType == Double)
                lResult = new ExpressionType(Double, false);
            else
                lResult = new ExpressionType(LongInteger, false);
        }
        else if(fOperation == Modulo)
        {
            lResult = new ExpressionType(LongInteger, false);
        }
        else if(fOperation == Plus)
        {
            if(lLeftValType == String)
                lResult = new ExpressionType(String, false);
            else if(lLeftValType == Double || lRightValType == Double)
                lResult = new ExpressionType(Double, false);
            else
                lResult = new ExpressionType(LongInteger, false);
        }
    }
    else
    {
        lResult = new ExpressionType(Unresolved);
    }

    delete lLeftType;
    delete lRightType;

    return lResult;
}

ExpressionValue* BinaryOperation::EvaluateNumericExpression(long iVal1, long iVal2, BinaryOperationType iOp)
{
    switch(iOp)
    {
        case Less:              return new ExpressionValue(iVal1 < iVal2);
        case Greater:           return new ExpressionValue(iVal1 > iVal2);
        case LessOrEqual:       return new ExpressionValue(iVal1 <= iVal2);
        case GreaterOrEqual:    return new ExpressionValue(iVal1 >= iVal2);
        case Equal:             return new ExpressionValue(iVal1 == iVal2);
        case NotEqual:          return new ExpressionValue(iVal1 != iVal2);
        case Minus:             return new ExpressionValue(iVal1 - iVal2);
        case Multiply:          return new ExpressionValue(iVal1 * iVal2);
        case Divide:            return new ExpressionValue(iVal1 / iVal2);
        case Modulo:            return new ExpressionValue(iVal1 % iVal2);
        case Plus:              return new ExpressionValue(iVal1 + iVal2);
        default:
            throw IncompatibleOperationAndTypesException("BinaryOperation::EvaluateNumericExpression", LongInteger, LongInteger, iOp);
    }
}

ExpressionValue* BinaryOperation::EvaluateNumericExpression(long iVal1, double iVal2, BinaryOperationType iOp)
{
    switch(iOp)
    {
        case Less:              return new ExpressionValue(iVal1 < iVal2);
        case Greater:           return new ExpressionValue(iVal1 > iVal2);
        case LessOrEqual:       return new ExpressionValue(iVal1 <= iVal2);
        case GreaterOrEqual:    return new ExpressionValue(iVal1 >= iVal2);
        case Equal:             return new ExpressionValue(iVal1 == iVal2);
        case NotEqual:          return new ExpressionValue(iVal1 != iVal2);
        case Minus:             return new ExpressionValue(iVal1 - iVal2);
        case Multiply:          return new ExpressionValue(iVal1 * iVal2);
        case Divide:            return new ExpressionValue(iVal1 / iVal2);
        case Plus:              return new ExpressionValue(iVal1 + iVal2);
        default:
            throw IncompatibleOperationAndTypesException("BinaryOperation::EvaluateNumericExpression", LongInteger, Double, iOp);
    }
}

ExpressionValue* BinaryOperation::EvaluateNumericExpression(double iVal1, long iVal2, BinaryOperationType iOp)
{
    switch(iOp)
    {
        case Less:              return new ExpressionValue(iVal1 < iVal2);
        case Greater:           return new ExpressionValue(iVal1 > iVal2);
        case LessOrEqual:       return new ExpressionValue(iVal1 <= iVal2);
        case GreaterOrEqual:    return new ExpressionValue(iVal1 >= iVal2);
        case Equal:             return new ExpressionValue(iVal1 == iVal2);
        case NotEqual:          return new ExpressionValue(iVal1 != iVal2);
        case Minus:             return new ExpressionValue(iVal1 - iVal2);
        case Multiply:          return new ExpressionValue(iVal1 * iVal2);
        case Divide:            return new ExpressionValue(iVal1 / iVal2);
        case Plus:              return new ExpressionValue(iVal1 + iVal2);
        default:
            throw IncompatibleOperationAndTypesException("BinaryOperation::EvaluateNumericExpression", Double, LongInteger, iOp);
    }
}

ExpressionValue* BinaryOperation::EvaluateNumericExpression(double iVal1, double iVal2, BinaryOperationType iOp)
{
    switch(iOp)
    {
        case Less:              return new ExpressionValue(iVal1 < iVal2);
        case Greater:           return new ExpressionValue(iVal1 > iVal2);
        case LessOrEqual:       return new ExpressionValue(iVal1 <= iVal2);
        case GreaterOrEqual:    return new ExpressionValue(iVal1 >= iVal2);
        case Equal:             return new ExpressionValue(iVal1 == iVal2);
        case NotEqual:          return new ExpressionValue(iVal1 != iVal2);
        case Minus:             return new ExpressionValue(iVal1 - iVal2);
        case Multiply:          return new ExpressionValue(iVal1 * iVal2);
        case Divide:            return new ExpressionValue(iVal1 / iVal2);
        case Plus:              return new ExpressionValue(iVal1 + iVal2);
        default:
            throw IncompatibleOperationAndTypesException("BinaryOperation::EvaluateNumericExpression", Double, Double, iOp);
    }
}

ExpressionValue* BinaryOperation::EvaluateStringExpression(string iVal1, string iVal2, BinaryOperationType iOp)
{
    switch(iOp)
    {
        case Equal:     return new ExpressionValue(iVal1 == iVal2);
        case NotEqual:  return new ExpressionValue(iVal1 != iVal2);
        case Plus:      return new ExpressionValue(new string(iVal1 + iVal2));
        default:
            throw IncompatibleOperationAndTypesException("BinaryOperation::EvaluateStringExpression", String, String, iOp);
    }
}

ExpressionValue* BinaryOperation::EvaluateBoolExpression(bool iVal1, bool iVal2, BinaryOperationType iOp)
{
    switch(iOp)
    {
        case Equal:     return new ExpressionValue(iVal1 == iVal2);
        case NotEqual:  return new ExpressionValue(iVal1 != iVal2);
        case And:       return new ExpressionValue(iVal1 && iVal2);
        case Or:        return new ExpressionValue(iVal1 || iVal2);
        default:
            throw IncompatibleOperationAndTypesException("BinaryOperation::EvaluateStringExpression", Boolean, Boolean, iOp);
    }
}

void BinaryOperation::Accept(GeneralVisitor* iVisitor)
{
    iVisitor->Visit(this);
}
